package net.jxtz.jxtzos.authentication;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * 重写request过滤器
 * 保证往下执行的过滤器能够重复读取request的输入流
 *
 * @author a123
 */
@Slf4j
public class HttpServletRequestWrapFilter extends OncePerRequestFilter {
    @Override
    protected synchronized void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        log.info("请求体过滤开始");
        final RequestBodyServletRequestWrapper requestWrapper = new RequestBodyServletRequestWrapper(httpServletRequest);
        filterChain.doFilter(requestWrapper, httpServletResponse);
    }

    /**
     *
     * ServletRequest 的包装器 (让后续方法可以重复调用 request.getInputStream())<br>
     * 处理流只能读取一次的问题, 用包装器继续将流写出. @RequestBody 会调用 getInputStream 方法, 所以本质上是解决 getInputStream 多次调用的问题: <br>
     * ServletRequest 的 getReader() 和 getInputStream() 两个方法只能被调用一次，而且不能两个都调用。那么如果 Filter 中调用了一次，在 Controller 里面就不能再调用了,
     * 会抛出异常:getReader() has already been called for this request 异常
     *
     */
    private static class RequestBodyServletRequestWrapper extends HttpServletRequestWrapper {


        /**
         * 请求体数据
         */
        private byte[] requestBody;
        /**
         * 重写的参数 Map
         */
        private final Map<String, String[]> paramMap;

        /**
         * Constructs a request object wrapping the given request.
         *
         * @param request The request to wrap
         * @throws IllegalArgumentException if the request is null
         */
        public RequestBodyServletRequestWrapper(HttpServletRequest request) {
            super(request);

            // 重写 requestBody
            try {
                requestBody = IOUtils.toByteArray(request.getReader(), StandardCharsets.UTF_8);
            } catch (IOException e) {
                e.printStackTrace();
            }

            // 重写参数 Map
            paramMap = new HashMap<>();

            if (requestBody.length == 0) {
                return;
            }

            JSONObject jsonObject = JSONObject.parseObject(StringUtils.toEncodedString(requestBody, StandardCharsets.UTF_8));
            jsonObject.forEach((key, value) -> paramMap.put(key, new String[]{String.valueOf(value)}));

        }


        public Map<String, String[]> getParamMap() {
            return paramMap;
        }

        @Override
        public String getParameter(String name) {
            String[] valueArr = paramMap.get(name);
            if (valueArr == null || valueArr.length == 0) {
                return null;
            }
            return valueArr[0];
        }

        @Override
        public String[] getParameterValues(String name) {

            return paramMap.get(name);
        }

        @Override
        public Enumeration<String> getParameterNames() {
            return Collections.enumeration(paramMap.keySet());
        }

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            final ByteArrayInputStream inputStream = new ByteArrayInputStream(requestBody);
            return new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return false;
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setReadListener(ReadListener listener) {

                }

                @Override
                public int read() throws IOException {
                    return inputStream.read();
                }
            };
        }
    }
}
